package net.aiion.weave;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;

import net.aiion.weave.asm.ClassReader;
import net.aiion.weave.asm.ClassWriter;
import net.aiion.weave.asm.FieldVisitor;
import net.aiion.weave.asm.MethodVisitor;
import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.asm.Type;
import net.aiion.weave.asm.util.ASMifier;
import net.aiion.weave.asm.util.TraceClassVisitor;

public class ConstructorSample implements Opcodes
{
    public static byte[] dump() {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fw;
        MethodVisitor mv;

        cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "foo", null, "net/aiion/weave/B", null);

        {
            fw = cw.visitField(ACC_PUBLIC, "a", "I", null, null);
            fw.visitEnd();
        }

        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Lnet/aiion/weave/B;)V", null, null);
            mv.visitCode();

            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("Hello World!");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");

            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKEVIRTUAL, "net/aiion/weave/B", "getI", "()I");
            mv.visitFieldInsn(PUTFIELD, "foo", "a", "I");

            mv.visitLdcInsn(Type.getType("Lfoo;"));
            mv.visitVarInsn(ASTORE, 2);

            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitVarInsn(ALOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "toString", "()Ljava/lang/String;");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");

            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "net/aiion/weave/B", "<init>", "()V");

            mv.visitInsn(RETURN);
            mv.visitMaxs(3, 3);
            mv.visitEnd();
        }

        {
            mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("Hello World from foo!");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(2, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
        IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
        SecurityException, NoSuchFieldException
    {
        ClassLoader cl = new CL(Thread.currentThread().getContextClassLoader());
        Class<?> c = cl.loadClass("foo");
        Object o = c.getConstructor(InterceptableClass.class).newInstance(new InterceptableClass());
        System.out.println("Value: " + c.getDeclaredField("a").get(o));
    }
}

class CL extends ClassLoader
{
    public CL() {
        super(ClassLoader.getSystemClassLoader());
    }

    public CL(final ClassLoader aParentClassLoader) {
        super(aParentClassLoader);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if ("foo".equals(name)) {
            byte[] classBytes = ConstructorSample.dump();

            // BEGIN DEBUG
            try {
                final ClassReader cr = new ClassReader(new ByteArrayInputStream(classBytes));
                final TraceClassVisitor cv = new TraceClassVisitor(null, new ASMifier(), new PrintWriter(
                    new OutputStreamWriter(System.out)));
                cr.accept(cv, 0);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // END DEBUG

            return defineClass(name, classBytes, 0, classBytes.length);
        }

        return null;
    }
}
